﻿using System;
using System.Threading;
using System.Xml;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

namespace WITD
{
    /// <summary>
    /// A class to interact with TFS 2010 global lists.
    /// </summary>
    class GlobalList
    {
        private String _globalListFilename;
        AutoResetEvent DialogRunning = new AutoResetEvent(false);
        System.Windows.Forms.DialogResult userAnswer;

        /// <summary>
        /// The name of the global list file.
        /// </summary>
        public String GlobalListFilename
        {
            get
            {
                return _globalListFilename;
            }
            set
            {
                _globalListFilename = value;
            }
        }
        
        /// <summary>
        /// Determines if the specified node contains a node with the specified name. if true, the <paramref name="refNode">refNode parameter</paramref> 
        /// is set to the node that was found.
        /// </summary>
        /// <param name="node">The name of the parent catNode, such as GLOBALLIST</param>
        /// <param name="name">The attributeValue of the name attribute, such as Builds - xxxx</param>
        /// <param name="xml">The xml document to search in</param>
        /// <param name="refNode">The named node if found</param>
        /// <returns>True if the document contains the node. Otherwise false;</returns>
        public static bool HasNode(String node, String name, XmlDocument xml, out XmlElement refNode)
        {
            foreach (XmlElement element in xml.GetElementsByTagName(node))
            {
                if (element.Attributes["name"].Value.ToUpper() == name.ToUpper())
                {
                    refNode = element;
                    return true;
                }
            }
            refNode = null;
            return false;
        }

        /// <summary>
        /// Determines if the specified node has an attribute with the specified attributeValue.
        /// </summary>
        /// <param name="node">The name of the parent catNode, such as GLOBALLIST</param>
        /// <param name="value">The attributeValue of the attributeValue attribute</param>
        /// <param name="xml">The xml document to search in</param>
        /// <returns>True if the named attribute is found. Otherwise false.</returns>
        public static bool HasNodeAttr(String node, String value, XmlDocument xml)
        {
            foreach (XmlElement ele in xml.GetElementsByTagName(node))
            {
                if (ele.Attributes["value"].Value.ToUpper() == value.ToUpper())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Creates a new element with the specified name and with an attribute with the specified attributeValue.
        /// </summary>
        /// <param name="globalList">The global list document to which the element is added.</param>
        /// <param name="elementName">The name of the node to add.</param>
        /// <param name="attributeValue">The value of the new node's attribute.</param>
        /// <param name="listElement">The owning node to which the new node is added.</param>
        public static void AddValue(XmlDocument globalList, string elementName, string attributeValue, XmlElement listElement)
        {
            XmlElement Item = (XmlElement)globalList.CreateElement(elementName);
            Item.SetAttribute("value", attributeValue);
            listElement.AppendChild(Item);
        }

        /// <summary>
        /// Imports the specified global lists into the specified WorkItemStore.
        /// </summary>
        /// <param name="store">The WorkItemStore to which the global lists are imported.</param>
        /// <param name="lists">The global lists to import.</param>
        public static void PutGlobalLists(WorkItemStore store, XmlDocument lists)
        {
            if (store != null && lists != null)
            {
                store.ImportGlobalLists(lists.DocumentElement);
            }
            else
            {
                return;
            }
        }

        /// <summary>
        /// Exports the global lists from the specified WorkItemStore.
        /// </summary>
        /// <param name="store">The WorkItemStore from which to export the global lists.</param>
        /// <returns>The global lists document.</returns>
        public static XmlDocument GetGlobalLists(WorkItemStore store)
        {
            if (store != null)
            {
                return store.ExportGlobalLists();
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Destroys the global lists from the specified collection, specified by Uri. Either all global lists are destroyed or a specified global list is removed.
        /// </summary>
        /// <param name="uri">The Uri of the collection from which to destroy the global lists.</param>
        /// <param name="xml">The global lists Xml from which to remove the named global list if not destroying all of them.</param>
        /// <param name="all">True to destroy the entire global lists document from the collection. Otherwise remove the named global list.</param>
        /// <param name="listName">The name of the global list to remove if not destroying all global lists.</param>
        /// <param name="message">Stores any error messages generated from this method call.</param>
        public void DestroyGlobalLists(String uri, XmlDocument xml, bool all, String listName, out String message)
        {
            int exitCode;
            String commandLine = String.Empty;
            TfsUtility utility = new TfsUtility();

            message = "";

            try
            {
                if (all)
                {
                    foreach (XmlElement node in xml.GetElementsByTagName("GLOBALLIST"))
                    {
                        commandLine = String.Format("destroygloballist /collection:{0} /n:\"{1}\" /noprompt /force", uri, node.Attributes["name"].Value.ToString());
                        exitCode = utility.RunProcess(utility.WitCommandName, commandLine);
                    }
                }
                else
                {
                    foreach (XmlElement node in xml.GetElementsByTagName("GLOBALLIST"))
                    {
                        if (node.Attributes["name"].Value.ToString() == listName)
                        {
                            commandLine = String.Format("destroygloballist /collection:{0} /n:\"{1}\" /noprompt /force", uri, node.Attributes["name"].Value.ToString());
                            exitCode = utility.RunProcess(utility.WitCommandName, commandLine);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //log instead
                message = ex.Message;
            }
        }


        /// <summary>
        /// Retrieves the number of global lists in the specified WorkItemStore.
        /// </summary>
        /// <param name="store">The WorkItemStore to query.</param>
        /// <returns>The number of global lists in the specified WorkItemStore.</returns>
        public Int32 GlobalListCount(WorkItemStore store)
        {
            XmlDocument xml = store.ExportGlobalLists();
            XmlNodeList nodes = xml.GetElementsByTagName("GLOBALLIST");
            return nodes.Count;
        }

        /// <summary>
        /// Copies the global lists from the specified source WorkItemStore to the specified target WorkItemStore, using the specified options.
        /// </summary>
        /// <param name="sourceStore">The source WorkItemStore.</param>
        /// <param name="targetStore">The target WorkItemStore.</param>
        /// <param name="glOptions">The <see cref="GlobalListCopyOptions">options</see> that determine how to copy the global lists.</param>
        /// <param name="message">An out parameter to pass back any error messages.</param>
        public void CopyGlobalLists(WorkItemStore sourceStore, WorkItemStore targetStore, GlobalListCopyOptions glOptions, out String message)
        {
            XmlDocument doc = null;
            TfsUtility utility = new TfsUtility();

            message = "";

            if (utility.CollectionsDifferent(sourceStore.TeamProjectCollection, targetStore.TeamProjectCollection))
            {
                sourceStore.RefreshCache();
                sourceStore.SyncToCache();
                targetStore.RefreshCache();
                targetStore.SyncToCache();
                userAnswer = System.Windows.Forms.DialogResult.None;

                try
                {
                    XmlDocument target = targetStore.ExportGlobalLists();
                    SaveGlobalList(target);

                    if (userAnswer.Equals(System.Windows.Forms.DialogResult.OK))
                    {
                        switch (glOptions.Options)
                        {
                            case GlobalListOptions.AddMissing:
                                doc = sourceStore.ExportGlobalLists();
                                break;

                            case GlobalListOptions.OverwriteAll:
                                doc = sourceStore.ExportGlobalLists();
                                DestroyGlobalLists(targetStore.TeamProjectCollection.Uri.ToString(), target, true, "", out message);
                                targetStore.RefreshCache();
                                targetStore.SyncToCache();
                                break;
                        }

                        targetStore.ImportGlobalLists(doc.DocumentElement);
                        targetStore.RefreshCache();
                        targetStore.SyncToCache();
                    }
                    else
                    {
                        //log instead
                        message = "User cancelled";
                    }
                }
                catch (Exception ex)
                {
                    message = "Error: " + ex.Message;
                }
            }
            else
            {
                //log instead
                message = "Cannot continue. Collections are not different.";
            }
        }

        /// <summary>
        /// Opens a <see cref="FileDialog">FileDialog</see> to let the user save the target global lists to a file before copying another global list over it.
        /// </summary>
        /// <param name="state">The <see cref="XmlDocument">Global List XML Document</see> to save.</param>
        [STAThread]
        public void FileDialog(Object state)
        {
            XmlDocument doc = (XmlDocument)state;
            
            System.Windows.Forms.SaveFileDialog dialog = new System.Windows.Forms.SaveFileDialog();

            dialog.Title = "Save global list before overwriting";
            dialog.DefaultExt = "xml";
            dialog.Filter = "Xml files (*.xml)|*.xml|All files (*.*)|*.*";
            dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            dialog.CheckFileExists = false;
            dialog.RestoreDirectory = true;
            dialog.OverwritePrompt = true;

            userAnswer = dialog.ShowDialog();

            if (userAnswer == System.Windows.Forms.DialogResult.OK)
            {
                doc.Save(dialog.FileName);
            }

            GlobalListFilename = dialog.FileName;

            if (dialog != null) { dialog.Dispose(); }
            DialogRunning.Set();

        }

        /// <summary>
        /// Entry point for saving the global list file. Must be set to STA thread. There does not seem to be a way to alter the owning thread to STA in WPF, so 
        /// in this manner a new thread can be created as STA and managed via AutoResetEvent object to signal.
        /// </summary>
        /// <param name="doc">The global list document to save to disk.</param>
        [STAThread()]
        public void SaveGlobalList(XmlDocument doc)
        {
            System.Threading.Thread saveFile = new Thread(FileDialog);
            saveFile.SetApartmentState(ApartmentState.STA);
            saveFile.Start((Object)doc);

            DialogRunning.WaitOne();
        }

    }
}
